//
//  UIStrokeView.m
//  StrokeDemo
//
//  Created by wankang on 2/13/15.
//  Copyright (c) 2015 wankang. All rights reserved.
//

#import "UIStrokeView.h"
#import "StrokeRecord.h"
#import "NSStrokeFill.h"

static CGFloat EPSILON = 1.0e-5f;

//////////////////////////////////////////////////////////////////////////////////

void setFillColor(unsigned char *pb, int clrFill)
{
    *(int *)pb = clrFill;
/*    pb[0] = (unsigned char)(clrFill & 0xff);
    pb[1] = (unsigned char)((clrFill >> 8) & 0xff);
    pb[2] = (unsigned char)((clrFill >> 16) & 0xff);
    pb[3] = (unsigned char)((clrFill >> 24) & 0xff);//*/
}

static int DIAMETER = 12;
BOOL checkConflict(int x, int y, NSArray *arr) {
    int nSize = (int)arr.count;
    if (nSize <= 0)
        return NO;
    for (int i=0; i<nSize; i++) {
        NSNumber *v = [arr objectAtIndex:i];
        int x1 = (int)(v.intValue & 0xffff);
        int y1 = (int)((v.intValue >> 16) & 0xffff);
        if (x1<x-DIAMETER || x1>x+DIAMETER || y1<y-DIAMETER || y1>y+DIAMETER)
            continue;
        return YES;
    }
    return NO;
}

//////////////////////////////////////////////////////////////////////////////////

@class NSStrokeFill;

@interface FillContext : NSObject<PlaySoundEndNotify> {
    
}
@property (nonatomic) NSStrokeFill *filler;
@property (nonatomic) BOOL          playSound;
@property (nonatomic) id<PlayChnSoundDelegate> delegate;
@property (nonatomic) id<StrokeDataDelegate> dataLoader;
@property (nonatomic) unsigned char *bData;
@property (nonatomic) NSUInteger    width;
@property (nonatomic) NSUInteger    height;
@property (nonatomic) BOOL          withImage;

-(id)init;
-(id)initWithFiller:(NSStrokeFill *)filler andDelegate:(id<PlayChnSoundDelegate>)delegate;
-(void)dealloc;
-(void)close;
-(void)onPlayEnd;
@end

//////////////////////////////////////////////////////////////////////////////////

@implementation FillContext
@synthesize filler = _filler;
@synthesize playSound = _playSound;
@synthesize delegate = _delegate;
@synthesize bData = _bData;
@synthesize width = _width;
@synthesize height = _height;
@synthesize withImage = _withImage;

-(id)init
{
    if (self = [super init]) {
        _filler = nil;
        _playSound = NO;
        _delegate = nil;
        _bData = NULL;
        _width = 0;
        _height = 0;
        _withImage = NO;
    }
    return self;
}

-(id)initWithFiller:(NSStrokeFill *)filler andDelegate:(id<PlayChnSoundDelegate>)delegate
{
    if (self = [super init]) {
        _filler = filler;
        _playSound = NO;
        _delegate = delegate;
        _bData = NULL;
        _width = 0;
        _height = 0;
        _withImage = NO;
    }
    return self;
}

-(void)dealloc
{
    [self close];
}

-(void)close
{
    _filler = nil;
    _delegate = nil;
    if (_bData != NULL) {
        free(_bData);
        _bData = NULL;
    }
}

-(void)onPlayEnd
{
    self.playSound = NO;
}
@end

//////////////////////////////////////////////////////////////////////////////////

@interface NSImageData : NSObject
@property (nonatomic) unsigned char *bData;
@property (nonatomic) NSUInteger width;
@property (nonatomic) NSUInteger height;

-(id)initImageData:(unsigned char *)pbData inWidth:(NSUInteger)width andHeight:(NSUInteger)height;
@end

//////////////////////////////////////////////////////////////////////////////////

@implementation NSImageData
@synthesize bData = _bData;
@synthesize width = _width;
@synthesize height = _height;

-(id)initImageData:(unsigned char *)pbData inWidth:(NSUInteger)width andHeight:(NSUInteger)height
{
    if (self = [super init]) {
        _bData = pbData;
        _width = width;
        _height = height;
    }
    return self;
}
@end

//////////////////////////////////////////////////////////////////////////////////
@implementation UIStrokeView
@synthesize drawContour = _drawContour;
@synthesize fillContour = _fillContour;
@synthesize drawMidline = _drawMidline;
@synthesize drawIndex = _drawIndex;
@synthesize playStrokeWave = _playStrokeWave;
@synthesize playChnWave = _playChnWave;
@synthesize maxWidth = _maxWidth;
@synthesize maxHeight = _maxHeight;
@synthesize clrBack = _clrBack;
@synthesize clrLine = _clrLine;
@synthesize clrBuShou = _clrBuShou;
@synthesize clrBuYu = _clrBuYu;
@synthesize clrMiddle = _clrMiddle;
@synthesize clrIndex = _clrIndex;
@synthesize fontIndex = _fontIndex;
@synthesize backImage = _backImage;
@synthesize timeUpdate = _timeUpdate;
@synthesize maxUpdate = _maxUpdate;
@synthesize delegate = _delegate;

-(NSString *)getCode
{
    return mCode;
}

-(id)init
{
    if (self = [super init]) {
        mCode = nil;
        mRecord = nil;
        mShowImage = nil;
        mStartPoint = nil;
        [self setDefaultData];
    }
    return self;
}

-(id)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super initWithCoder:aDecoder]) {
        mCode = nil;
        mRecord = nil;
        mShowImage = nil;
        mStartPoint = nil;
        [self setDefaultData];
    }
    return self;
}

-(id)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        mCode = nil;
        mRecord = nil;
        mShowImage = nil;
        mStartPoint = nil;
        [self setDefaultData];
    }
    return self;
}

-(void)dealloc
{
    [self close];
}

-(void)close
{
    [self stopFilling];
    mCode = nil;
    mRecord = nil;
    mShowImage = nil;
    mStartPoint = nil;
    _fontIndex = nil;
}

///////////////////////////////////////////////////////////////////////////////

-(BOOL)clearShow
{
    if (![self stopFilling])
        return NO;
    mCode = nil;
    mRecord = nil;
    mShowImage = nil;
    [self setNeedsDisplay];
    return YES;
}

-(void)setOptionForContour:(BOOL)drawContour fillContour:(BOOL)fill midline:(BOOL)drawMidline andIndex:(BOOL)drawIndex
{
    _drawContour = drawContour;
    _fillContour = fill;
    _drawMidline = drawMidline;
    _drawIndex = drawIndex;
    if (mRecord != nil) {
        [self setNeedsDisplay];
    }
}

-(BOOL)setCode:(NSString *)sCode
{
    if (![self loadStrokeForChnCode:sCode]) {
        NSLog(@"fail to load for Chn:%@",sCode);
        return NO;
    }
    [self setNeedsDisplay];
    return YES;
}

-(BOOL)fillStroke:(NSString *)sCode withDelegate:(id<PlayChnSoundDelegate>)delegate
{
    if (![self loadStrokeForChnCode:sCode])
        return NO;
    NSStrokeFill *filler = [[NSStrokeFill alloc] init];
    FillContext *context = [[FillContext alloc] initWithFiller:filler andDelegate:delegate];
    context.dataLoader = _delegate;
    //[self initImageForFillContext:context inThread:NO];
    mFillThread = [[NSThread alloc] initWithTarget:self selector:@selector(fillThreadProc:) object:context];
    if (mFillThread == nil)
        return NO;
    mThreadFinish = NO;
    [mFillThread start];
    return YES;
}

-(BOOL)loadStrokeForChnCode:(NSString *)sCode
{
    // simple argument check
    if (sCode==nil || sCode.length!=1 || _delegate==nil)
        return NO;
    // to stop filling
    if (mFillThread != nil) {
        if (![self stopFilling])
            return NO;
    }
    mShowImage = nil;
    mStopThread = NO;
    // to load record
    if (![sCode isEqualToString:mCode]) {
        mRecord = [_delegate getStrokeForChn:sCode width:_maxWidth height:_maxHeight];
        mCode = sCode;
        if (_drawIndex) {
            [self createStartPoint];
        }
    }
    // whether it is empty or not
    if (mRecord == nil || mRecord.count == 0)
        return NO;
    return YES;
}

-(BOOL)createStartPoint
{
    if (mRecord==nil || mRecord.count==0)
        return NO;
    BOOL fSkipContour = NO;
    int nContour = (int)mRecord.count;
    NSMutableArray *arr = [[NSMutableArray alloc] initWithCapacity:nContour];
    for (int i=0; i<nContour; i++) {
        StrokeRecord *data = [mRecord objectAtIndex:i];
        if (i==0 || !fSkipContour) {
            int x = (int)data.xStart;
            int y = (int)data.yStart;
            if (checkConflict(x, y, arr)) {
                if (x+8<_maxWidth && !checkConflict(x+8, y, arr)) {
                    x += 8;
                } else if (y+8<_maxHeight && !checkConflict(x, y+8, arr)) {
                    y += 8;
                } else if (x-8>=0 && !checkConflict(x-8, y, arr)) {
                    x -= 8;
                } else if (y-8>=0 && !checkConflict(x, y-8, arr)) {
                    y -= 8;
                }
            }
            int value = x + ( y << 16 );
            NSNumber *v = [ [NSNumber alloc] initWithInt:value ];
            [arr addObject:v];
        }
        fSkipContour = ![data isStrokeEnd];
    }
    mStartPoint = arr;
    return YES;
}

-(void)initImageForFillContext:(FillContext *)context inThread:(BOOL)fThreadCall{
    NSUInteger width, height;
    CGSize size = self.frame.size;
    // see: http://stackoverflow.com/questions/4334233/how-to-capture-uiview-to-uiimage-without-loss-of-quality-on-retina-display
    UIGraphicsBeginImageContextWithOptions(size, self.opaque, 0);
    //[self.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    unsigned char *pbData = NULL;
    if (image != nil) {
        if (fThreadCall)
            pbData = [self getBackgroundImageData:image withWidth:&width andHeight:&height];
        else
            pbData = [UIStrokeView getRGBABytes:image withWidth:&width andHeight:&height];
    }
    UIGraphicsEndImageContext();
    if (pbData != NULL) {
        context.bData = pbData;
        context.width = width;
        context.height = height;
    }
    context.withImage = YES;
}

-(BOOL)stopFilling
{
    if (mFillThread != nil) {
        mStopThread = YES;
        int nMaxLoop = 10;
        while (!mThreadFinish && nMaxLoop>0) {
            [NSThread sleepForTimeInterval:0.01];
            nMaxLoop--;
        }
        if (nMaxLoop <= 0)
            return NO;
        mFillThread = nil;
    }
    mShowImage = nil;
    //[self setNeedsDisplay];
    return YES;
}

///////////////////////////////////////////////////////////////////////////////

+(NSArray *)getRGBAsFromImage:(UIImage *)image atX:(int)x andY:(int)y count:(int)count
{
    // code from:
    // http://stackoverflow.com/questions/448125/how-to-get-pixel-data-from-a-uiimage-cocoa-touch-or-cgimage-core-graphics
    NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];
    // First get the image into your data buffer
    NSUInteger width, height;
    unsigned char *rawData = [UIStrokeView getRGBABytes:image withWidth:&width andHeight:&height];
    // Now your rawData contains the image data in the RGBA8888 pixel format.
    NSUInteger bytesPerRow = 4 * width;
    NSUInteger byteIndex = (bytesPerRow * y) + x * 4;
    CGFloat fRatio = 1 / 255.0;
    for (int i = 0 ; i < count ; ++i) {
        CGFloat red   = rawData[byteIndex + 0] * fRatio;
        CGFloat green = rawData[byteIndex + 1] * fRatio;
        CGFloat blue  = rawData[byteIndex + 2] * fRatio;
        CGFloat alpha = rawData[byteIndex + 3] * fRatio;
        byteIndex += 4;
        UIColor *acolor = [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
        [result addObject:acolor];
    }
    free(rawData);
    return result;
}

///////////////////////////////////////////////////////////////////////////////

+(unsigned char *)getRGBABytes:(UIImage *)image withWidth:(NSUInteger *)pnX andHeight:(NSUInteger *)pnY
{
    if (image == nil)
        return NULL;
    CGImageRef imageRef = [image CGImage];
    NSUInteger width = CGImageGetWidth(imageRef);
    NSUInteger height = CGImageGetHeight(imageRef);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    unsigned char *rawData = (unsigned char *) calloc(height * width * 4, sizeof(unsigned char));
    NSUInteger bytesPerRow = 4 * width;
    NSUInteger bitsPerComponent = 8;
    CGContextRef context = CGBitmapContextCreate(rawData, width, height, bitsPerComponent, bytesPerRow, colorSpace,
                                                 kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    CGColorSpaceRelease(colorSpace);
    CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
    CGContextRelease(context);
    // to output width and height
    *pnX = width;
    *pnY = height;
    return rawData;
}

-(void)setDefaultData
{
    _drawContour = YES;
    _fillContour = NO;
    _drawMidline = NO;
    _drawIndex = YES;
    _playStrokeWave = YES;
    _playChnWave = YES;
    
    mLeftMargin = 0;
    mTopMargin = 0;
    mStopThread = NO;
    mThreadFinish = YES;
    
    _maxWidth = 267;
    _maxHeight = 267;
    _clrBack = 0xffffffff;
    _clrLine = 0xffff0000;
    _clrBuShou = 0xffe18e00;
    _clrBuYu = 0xff663300;
    _clrMiddle = 0xff00ff00;
    _clrIndex = 0xff0000ff;
    _fontIndex = @"Courier";
    _backImage = BACK_GRID_MI;
    _timeUpdate = 0.02f;
    _maxUpdate = 40;
}

-(unsigned char *)getBackgroundImageData:(UIImage *)image withWidth:(NSUInteger *)pnWidth andHeight:(NSUInteger *)pnHeight
{
    CGImageRef imageRef = image.CGImage;
    NSUInteger width = CGImageGetWidth(imageRef);
    NSUInteger height = CGImageGetHeight(imageRef);
    unsigned char *rawData = (unsigned char *) calloc(height * width * 4, sizeof(unsigned char));
    if (rawData == NULL)
        return NULL;
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(rawData, width, height, 8, width * 4, colorSpace,
                                                 kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
    CGContextTranslateCTM(context, 0, height);
    CGSize sizeView = self.frame.size;
    // must add (.) for yScale because height is an unsigned integer
    CGContextScaleCTM(context, width / sizeView.width, -(height / sizeView.height));
    CGRect rect = CGRectMake(0, 0, sizeView.width, sizeView.height);
    //CGContextDrawImage(context, rect, imageRef);
    [self drawBackground:rect andStroke:NO inContext:context];
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);
    // to output width/height
    *pnWidth = width;
    *pnHeight = height;
    return rawData;
}

-(void)fillThreadProc:(FillContext *)context
{
    @autoreleasepool {
        // to get a reference of showing image
        [self initImageForFillContext:context inThread:YES];
        // to get image buffer
        if (!context.bData)
            return;
        NSUInteger width = context.width;
        NSUInteger height = context.height;
        // to get scale parameters
        CGSize size = self.frame.size;
        [self fillAllStroke:context xScale:width / size.width yScale:height / size.height];
        // to play sound for this Chn code
        if (_playChnWave) {
            [self playChn:mCode inFillContext:context];
        }
        // to clear image data, restore self
        if (!mStopThread) {
            [self performSelectorOnMainThread:@selector(updateImageWithRawData:) withObject:nil waitUntilDone:NO];
        }
        [context close];
        mThreadFinish = YES;
    }
}

-(void)fillAllStroke:(FillContext *)context xScale:(CGFloat)fXScale yScale:(CGFloat)fYScale
{
    unsigned char *pbData = context.bData;
    NSStrokeFill *filler = context.filler;
    // to set by scale parameters
    int xMargin = (int)(mLeftMargin * fXScale + EPSILON);
    int yMargin = (int)(mTopMargin * fYScale + EPSILON);
    filler.maxWidth = (int)(_maxWidth * fXScale + EPSILON);
    filler.maxHeight = (int)(_maxHeight * fYScale + EPSILON);
    // to fill by contour
    StrokePoint ptList[256];
    int nMaxUpdate = (int)MIN(_maxUpdate*fXScale, 256);
    int nContourCount = (int)mRecord.count;
    volatile BOOL *pbStop = &mStopThread;
    for (int nContourIndex=0; !*pbStop && nContourIndex<nContourCount; nContourIndex++) {
        StrokeRecord *rec = [mRecord objectAtIndex:nContourIndex];
        [self prepareFill:filler forIndex:nContourIndex withStrokeRecord:rec xScale:fXScale yScale:fYScale];
        int clrFill = (int)([rec isBuShou] ? _clrBuShou : _clrBuYu);
        // to update image data
        int nPoint;
        while(!*pbStop && (nPoint=(int)[filler getPoints:ptList withMaximumCount:nMaxUpdate])>0) {
            int nLineBytes = (int)(context.width * 4);
            if (nPoint > 0) {
                for (int m=0; !*pbStop && m<nPoint; m++) {
                    int x = ptList[m].x + xMargin;
                    int y = ptList[m].y + yMargin;
                    setFillColor(pbData + nLineBytes * y + x * 4, clrFill);
                }
            }
            if (*pbStop)
                break;
            // to update image on main thread
            unsigned char *pb = (unsigned char *)malloc(context.width * context.height * 4);
            memcpy(pb, pbData, context.width * context.height * 4);
            NSImageData *data = [[NSImageData alloc] initImageData:pb inWidth:context.width andHeight:context.height];
            [self performSelectorOnMainThread:@selector(updateImageWithRawData:) withObject:data waitUntilDone:NO];
            if (!*pbStop) {
                // at least wait 1ms
                [NSThread sleepForTimeInterval:MAX(_timeUpdate, 0.001f)];
            }
        }
        if (![rec isStrokeEnd])
            continue;
        // to play sound for stroke end
        if (_playStrokeWave) {
            [self playStroke:rec.nameStroke inFillContext:context];
        } else {
            // at least, wait 0.5 second
            int nCount = 10;
            while(!*pbStop && nCount>0) {
                [NSThread sleepForTimeInterval:0.05f];
                nCount--;
            }
        }
    }
}

-(void)prepareFill:(NSStrokeFill *)filler forIndex:(NSInteger)nContourIndex withStrokeRecord:(StrokeRecord *)rec
            xScale:(CGFloat)fXScale yScale:(CGFloat)fYScale
{
    if (fabs(fXScale - 1) > EPSILON || fabs(fYScale - 1) > EPSILON) {
        NSArray *arr1 = [StrokeRecord createScaledPoint:rec.arrContour withXScale:fXScale andYScale:fYScale];
        NSArray *arr2 = [StrokeRecord createScaledPoint:rec.arrMiddle withXScale:fXScale andYScale:fYScale];
        [filler setContour:arr1 andMiddle:arr2];
    } else {
        [filler setContour:rec.arrContour andMiddle:rec.arrMiddle];
    }
    // it is possible that there is no guide data for stroke filling
    NSArray *guideData = nil;
    if (_delegate!=nil && [_delegate respondsToSelector:@selector(getGuideForChn:withContourIndex:width:height:)]) {
        NSString *sNameStroke = rec.nameStroke;
        if (sNameStroke!=nil && sNameStroke.length>1) {
            int width = (int)(_maxWidth * fXScale + 0.5f);
            int height = (int)(_maxHeight * fYScale + 0.5f);
            guideData = [_delegate getGuideForChn:rec.code withContourIndex:nContourIndex width:width height:height];
        }
    }
    [filler setGuide:guideData];
    // to set start point for default case
    StrokePoint ptStart;
    ptStart.x = (short)(rec.xStart * fXScale + EPSILON);
    ptStart.y = (short)(rec.yStart * fYScale + EPSILON);
    [filler setStartPoint:ptStart];
}

-(void)playStroke:(NSString *)sName inFillContext:(FillContext *)context
{
    volatile BOOL *pbStop = &mStopThread;
    id<PlayChnSoundDelegate> delegate = context.delegate;
    if (delegate != nil && !*pbStop) {
        context.playSound = YES;
        [delegate playSound:sName forStroke:YES withNotify:context];
        // wait till playSound is .F.
        while(!*pbStop && context.playSound) {
            [NSThread sleepForTimeInterval:0.05f];
        }
    } else {
        //NSLog(@"skip playChn:%@", sName); //telen
    }
}

-(void)playChn:(NSString *)sCode inFillContext:(FillContext *)context
{
    volatile BOOL *pbStop = &mStopThread;
    id<PlayChnSoundDelegate> delegate = context.delegate;
    if (delegate != nil && !*pbStop) {
        // to wait a minute, about 0.5 second
        int n = 0;
        while(!*pbStop && n<10) {
            [NSThread sleepForTimeInterval:0.05f];
            n++;
        }
        // to play sound for Chn code
        if (sCode != nil && sCode.length > 0) {
            context.playSound = YES;
            [delegate playSound:sCode forStroke:NO withNotify:context];
        }
        // wait till playSound is .F.
        while(!*pbStop && context.playSound) {
            [NSThread sleepForTimeInterval:0.05f];
        }
    } else {
        //NSLog(@"skip playChn:%@", mCode); //telen 
    }
}

-(void)updateImageWithRawData:(NSImageData *)imgData
{
    mShowImage = nil;
    if (imgData != nil) {
        if (mStopThread || mThreadFinish) {
            free(imgData.bData);
            return;
        }
        UIImage *image = [UIStrokeView createImageFromData:imgData.bData withWidth:imgData.width andHeight:imgData.height];
        if (image != nil) {
            mShowImage = image;
        }
        free(imgData.bData);
    } else {
        mFillThread = nil;
    }
    [self setNeedsDisplay];
}

///////////////////////////////////////////////////////////////////////////////

+(CGColorRef)colorFromInt:(NSInteger)nValue inSpace:(CGColorSpaceRef)colorspace
{
    CGFloat components[4];
    components[0] = (nValue & 0xff) / 255.0f;
    components[1] = ((nValue>>8) & 0xff) / 255.0f;
    components[2] = ((nValue>>16) & 0xff) / 255.0f;
    components[3] = ((nValue>>24) & 0xff) / 255.0f;
    return CGColorCreate(colorspace, components);
}

+(UIImage *)createImageFromData:(unsigned char *)rawData withWidth:(NSUInteger)width andHeight:(NSUInteger)height
{
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(rawData, width, height, 8, width * 4, colorSpace,
                                             (CGBitmapInfo)kCGImageAlphaPremultipliedLast );
    UIImage *rawImage = nil;
    CGImageRef imageRef = CGBitmapContextCreateImage(context);
    if (imageRef != nil) {
        rawImage = [UIImage imageWithCGImage:imageRef];
        CGImageRelease(imageRef);
    }
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);
    return rawImage;
}

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
-(void)drawRect:(CGRect)rect {
    // Drawing code
    if (mShowImage != nil) {
        [mShowImage drawInRect:rect];
    } else {
        CGSize size = self.frame.size;
        mLeftMargin = (int)(size.width - _maxWidth) / 2;
        mTopMargin = (int)(size.height - _maxHeight) / 2;
        CGContextRef context = UIGraphicsGetCurrentContext();
        [self drawBackground:rect andStroke:YES inContext:context];
    }
}

-(void)drawBackground:(CGRect)rect andStroke:(BOOL)fDrawStroke inContext:(CGContextRef)context {
    CGColorSpaceRef clrSpace = CGColorSpaceCreateDeviceRGB();
    // fill with background color
    if ((_clrBack & 0xff000000) != 0) {
        CGColorRef clrBackFill = [UIStrokeView colorFromInt:_clrBack inSpace:clrSpace];
        CGContextSetFillColorWithColor(context, clrBackFill);
        CGContextFillRect(context, rect);
        CGColorRelease(clrBackFill);
    }
    // to show grid
    if ((_backImage != BACK_NONE) && ((_clrLine & 0xff000000)!=0)) {
        CGColorRef clrBackLine = [UIStrokeView colorFromInt:_clrLine inSpace:clrSpace];
        [self showBackGridWithColor:clrBackLine inContext:context];
        CGColorRelease(clrBackLine);
    }
    if (fDrawStroke && mRecord != nil && mRecord.count > 0) {
        // to show contour
        if (_drawContour) {
            CGColorRef clrBuShou = [UIStrokeView colorFromInt:_clrBuShou inSpace:clrSpace];
            CGColorRef clrBuYu = [UIStrokeView colorFromInt:_clrBuYu inSpace:clrSpace];
            [self showContourLineWithBuShouColor:clrBuShou andBuYuColor:clrBuYu inContext:context];
            CGColorRelease(clrBuShou);
            CGColorRelease(clrBuYu);
        }
        // to show middle line
        if (_drawMidline) {
            CGColorRef colorMidLine = [UIStrokeView colorFromInt:_clrMiddle inSpace:clrSpace];
            [self showMiddleLineWithColor:colorMidLine inContext:context];
            CGColorRelease(colorMidLine);
        }
        // to show stroke index at the start point
        if (_drawIndex && mStartPoint!=nil) {
            CGColorRef clrText = [UIStrokeView colorFromInt:_clrIndex inSpace:clrSpace];
            int clrBack = (int)((_clrBack & 0xff000000)!=0 ? _clrBack : 0xffffffff);
            CGColorRef clrFill = [UIStrokeView colorFromInt:clrBack inSpace:clrSpace];
            [self showStrokeIndexWithColor:clrText fontSize:12.0f clrFill:clrFill inContext:context];
            CGColorRelease(clrFill);
            CGColorRelease(clrText);
        }
    }
    CGColorSpaceRelease(clrSpace);
}

-(void)showContourLineWithBuShouColor:(CGColorRef)clrBuShou andBuYuColor:(CGColorRef)clrBuYu inContext:(CGContextRef)context
{
    StrokePoint pt0, pt;
    int nCount = (int)mRecord.count;
    CGContextSetLineWidth(context, 1.0f);
    CGContextSetAllowsAntialiasing(context, true);
    for (int i=0; i < nCount; i++) {
        StrokeRecord *rec = [mRecord objectAtIndex:i];
        if (![rec getContour:&pt0 fromIndex:0])
            continue;
        CGColorRef color = ([rec isBuShou] ? clrBuShou : clrBuYu);
        CGContextSetStrokeColorWithColor(context, color);
        CGContextMoveToPoint(context, (mLeftMargin + pt0.x), (mTopMargin + pt0.y));
        int nPoint = (int)rec.arrContour.count;
        for (int j=1; j < nPoint; j++) {
            if (![rec getContour:&pt fromIndex:j])
                break;
            CGContextAddLineToPoint(context, (mLeftMargin + pt.x), (mTopMargin + pt.y));
        }
        CGContextAddLineToPoint(context, (mLeftMargin + pt0.x), (mTopMargin + pt0.y));
        if (_fillContour) {
            CGContextSetFillColorWithColor(context, color);
            CGContextDrawPath(context, kCGPathFillStroke);
        } else {
            CGContextDrawPath(context, kCGPathStroke);
        }
    }
    CGContextSetAllowsAntialiasing(context, false);
}

-(void)showMiddleLineWithColor:(CGColorRef)color inContext:(CGContextRef)context
{
    StrokePoint pt;
    int nCount = (int)mRecord.count;
    CGContextSetLineWidth(context, 2.0f);
    CGContextSetStrokeColorWithColor(context, color);
    for (int i=0; i < nCount; i++) {
        StrokeRecord *rec = [mRecord objectAtIndex:i];
        if (![rec getMidline:&pt fromIndex:0])
            continue;
        CGContextMoveToPoint(context, (mLeftMargin + pt.x), (mTopMargin + pt.y));
        int nPoint = (int)rec.arrMiddle.count;
        for (int j=1; j < nPoint; j++) {
            if (![rec getMidline:&pt fromIndex:j])
                break;
            CGContextAddLineToPoint(context, (mLeftMargin + pt.x), (mTopMargin + pt.y));
        }
        CGContextStrokePath(context);
    }
}

-(void)showStrokeIndexWithColor:(CGColorRef)clrText fontSize:(CGFloat)fSize clrFill:(CGColorRef)fillColor inContext:(CGContextRef)context
{
    // to prepare showing style
    NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
    [style setAlignment:NSTextAlignmentCenter];
    UIFont *font = [UIFont fontWithName:_fontIndex size:fSize];
    UIColor *clrShow = [UIColor colorWithCGColor:clrText];
    NSDictionary *dictionary = @{ NSFontAttributeName: font,
                                  NSForegroundColorAttributeName: clrShow,
                                  NSStrokeColorAttributeName: clrShow,
                                  NSParagraphStyleAttributeName: style};
    // to show all stroke index
    int nStroke = (int)mStartPoint.count;
    for (int i=0; i<nStroke; i++) {
        int nEnlarge = (i+1>=10 ? 2 : 1);
        NSString *sText = [NSString stringWithFormat:@"%d", (i+1)];
        NSNumber *v = [mStartPoint objectAtIndex:i];
        short x = (short)(v.intValue & 0xffff);
        short y = (short)((v.intValue >> 16) & 0xffff);
        // to show white circle
        CGRect rCircle = CGRectMake(mLeftMargin+x-fSize/2, mTopMargin+y-fSize/2, fSize, fSize);
        CGContextAddEllipseInRect(context, rCircle);
        CGContextSetFillColorWithColor(context, fillColor);
        CGContextFillPath(context);
        // to show stroke index
        CGRect rectText = CGRectMake(mLeftMargin+x-fSize*nEnlarge/2, mTopMargin+y-fSize/2, fSize * nEnlarge, fSize);
        [sText drawInRect:rectText withAttributes:dictionary];
    }
}

-(void)showBackGrid:(StrokeBack)gridType inContext:(CGContextRef)context
{
    int nMaxWidth = (int)_maxWidth;
    int nMaxHeight = (int)_maxHeight;
    CGFloat xMargin = mLeftMargin;
    CGFloat yMargin = mTopMargin;
    CGFloat ra[] = {4.0f, 2.0f};
    switch(gridType) {
        case BACK_GRID_KOU:
            CGContextMoveToPoint(context, xMargin, yMargin);
            CGContextAddLineToPoint(context, xMargin+nMaxWidth, yMargin);
            CGContextAddLineToPoint(context, xMargin+nMaxWidth, yMargin+nMaxHeight);
            CGContextAddLineToPoint(context, xMargin, yMargin+nMaxHeight);
            CGContextAddLineToPoint(context, xMargin, yMargin);
            CGContextDrawPath(context, kCGPathStroke);
            break;
        case BACK_GRID_TIAN:
            [self showBackGrid:BACK_GRID_KOU inContext:context];
            CGContextSetLineDash(context, 0.0f, ra, 2);
            CGContextMoveToPoint(context, xMargin, yMargin+nMaxHeight/2.0f);
            CGContextAddLineToPoint(context, xMargin+nMaxWidth, yMargin+nMaxHeight/2.0f);
            CGContextDrawPath(context, kCGPathStroke);
            CGContextMoveToPoint(context, xMargin+nMaxWidth/2.0f, yMargin);
            CGContextAddLineToPoint(context, xMargin+nMaxWidth/2.0f, yMargin+nMaxHeight);
            CGContextDrawPath(context, kCGPathStroke);
            break;
        case BACK_GRID_MI:
            [self showBackGrid:BACK_GRID_TIAN inContext:context];
            CGContextMoveToPoint(context, xMargin, yMargin);
            CGContextAddLineToPoint(context, xMargin+nMaxWidth, yMargin+nMaxHeight);
            CGContextDrawPath(context, kCGPathStroke);
            CGContextMoveToPoint(context, xMargin+nMaxWidth, yMargin);
            CGContextAddLineToPoint(context, xMargin, yMargin+nMaxHeight);
            CGContextDrawPath(context, kCGPathStroke);
            break;
        case BACK_GRID_JING:
            [self showBackGrid:BACK_GRID_KOU inContext:context];
            CGContextSetLineDash(context, 0.0f, ra, 2);
            CGContextMoveToPoint(context, xMargin, yMargin+nMaxHeight/3.0f);
            CGContextAddLineToPoint(context, xMargin+nMaxWidth, yMargin+nMaxHeight/3.0f);
            CGContextDrawPath(context, kCGPathStroke);
            CGContextMoveToPoint(context, xMargin, yMargin+nMaxHeight*2/3.0f);
            CGContextAddLineToPoint(context, xMargin+nMaxWidth, yMargin+nMaxHeight*2/3.0f);
            CGContextDrawPath(context, kCGPathStroke);
            CGContextMoveToPoint(context, xMargin+nMaxWidth/3.0f, yMargin);
            CGContextAddLineToPoint(context, xMargin+nMaxWidth/3.0f, yMargin+nMaxHeight);
            CGContextDrawPath(context, kCGPathStroke);
            CGContextMoveToPoint(context, xMargin+nMaxWidth*2/3.0f, yMargin);
            CGContextAddLineToPoint(context, xMargin+nMaxWidth*2/3.0f, yMargin+nMaxHeight);
            CGContextDrawPath(context, kCGPathStroke);
            break;
        default:
            break;
    }
}

-(void)showBackGridWithColor:(CGColorRef)color inContext:(CGContextRef)context
{
    CGContextSetStrokeColorWithColor(context, color);
    CGContextSetLineDash(context, 0.0f, nil, 0);
    [self showBackGrid:_backImage inContext:context];
    CGContextSetLineDash(context, 0.0f, nil, 0);
}
@end
